// *********************************************************
//      Copyright (c) Kosta Gaitanis
//      All rights reserved
//
//      k.gaitanis@gmail.com
//
// *********************************************************

#ifndef MATRIX_H
#define MATRIX_H

#include "Vector.h"
#include <QtOpenGL/QtOpenGL>

/*! \class Matrix
 * \brief Spacial transformation matrix.
 * This class represents a 4x4 matrix used for spacial
 * transformations.
 * \author Arthur Benilov
 * \author Sebastien Brousmiche
 * \author Kosta Gaitanis
 */
class Matrix
{
public:
    /// Predefined identity matrix.
    static const Matrix Identity;

    /**
     * Construct a matrix by its elements.
     */
    Matrix(
            double s00, double s01, double s02, double s03,
            double s10, double s11, double s12, double s13,
            double s20, double s21, double s22, double s23,
            double s30, double s31, double s32, double s33);

    /**
     * Construct a diagonal matrix.
     * @param d Value of diagonal elements.
     */
    Matrix(double d = 1.0);

    /**
     * Copy constructor.
     * @param matrix Matrix to be copied.
     */
    Matrix(const Matrix &matrix);

    /**
     * Matrix assignment operator.
     * @param matrix Reference to another matrix to be copied.
     * @return this matrix.
     */
    Matrix& operator =(const Matrix &matrix);

    /**
     * Return a matrix row array.
     * This method does not check index bounds.
     * @param i Matrix row index (0..3).
     * @return Matrix row array.
     */
    const double * operator [](int i) const;

    /**
     * Returns a matrix single element.
     * @param row Row index (0..3).
     * @param col Column index (0..3).
     * @return Matrix element at specified position.
     */
    double get(int row, int col) const;

    /**
     * @brief sets the element of matrix at row/col to value
     * @param row a number between 0 and 3
     * @param col a number between 0 and 3
     * @param value The value to be inserted in the matrix.
     */
    void set(int row, int col, double value);

    /**
     * Returns a matrix single element.
     * @param row Row index (0..3).
     * @param col Column index (0..3).
     * @return Matrix element at specified position.
     */
    double operator()(int row, int col) const;

    /**
     * Perform matrix multiplication.
     * If the matrix represents a spacial transformation, another
     * matrix given in the argument will represent the next transformation
     * to be performed.
     * @param matrix Reference to another matrix.
     * @return Result of matrix multiplication.
     */
    Matrix operator *(const Matrix &matrix) const;

    /**
     * Multiply this matrix by a vector.
     * This method actually applies a transformation to a vector.
     * @param vector Reference to a vector to be transformed.
     * @return Transformed vector.
     */
    Vector operator *(const Vector &vector) const;

    /**
     * Convert this matrix into array of GLdouble values and inserts it into
     * array.
     * This method is convenient to transform matrix object
     * of double values to an array of GLdoubles.
     *
     * @param array A pointer to the first element of an array of length 16.
     *              This array will contain the values of the matrix in
     *              OpenGL ready to use format.
     */
    void toGLArray(GLdouble *array) const;

    /**
     * @brief Creates a matrix from the given OpenGL array and returns the matrix.
     *
     * @param array A 16-element array containing the values of the matrix in
     *              OpenGL format.
     * @return A matrix containing the elements of the array.
     */
    static Matrix fromGLArray(const GLdouble *array);

    /**
     * Compare elements of this matrix with another one.
     * @param matrix Reference to another matrix.
     * @return true if matrix elements are mutually equal within a tolerance.
     */
    bool operator ==(const Matrix &matrix) const;

    /**
     * Compare elements of this matrix with another one.
     * @param matrix Reference to another matrix.
     * @return false if matrix elements are mutually equal within a tolerance.
     */
    bool operator !=(const Matrix &matrix) const;

    /**
     * The transposed matrix contains the same values as this matrix but
     * the rows become columns and the columns become rows.
     *
     * @return The transposed matrix.
     */
    Matrix transposed() const;

    /**
     * Create translation matrix.
     * @param  {x, y, z} Translate by x,y,z
     * @return Translation matrix.
     */
    static Matrix translation(double x , double y , double z);

    /**
     * Create translation matrix.
     * @param  vector Reference to translation vector.
     * @return Translation matrix.
     */
    static Matrix translation(const Vector& vector);

    /**
     * Create X-axis rotation matrix.
     * @param angle Rotation angle in radians.
     * @return X-axis rotation matrix.
     */
    static Matrix rotationX(double angle);

    /**
     * Create Y-axis rotation matrix.
     * @param angle Rotation angle in radians.
     * @return Y-axis rotation matrix.
     */
    static Matrix rotationY(double angle);

    /**
     * Create Z-axis rotation matrix.
     * @param angle Rotation angle in radians.
     * @return Z-axis rotation matrix.
     */
    static Matrix rotationZ(double angle);

    /**
     * Returns a rotation matrix for arbitrary axis.
     * The rotation axis must be non-zero unit vector, elsewhere
     * it's absolute value will affect the rotation angle.
     *
     * @param {x,y,z} the rotation vector coordinates.
     * @param angle Rotation angle in radians.
     * @return Rotation matrix.
     */
    static Matrix rotation(double angle, double x, double y, double z);

    /**
     * Returns a rotation matrix for arbitrary axis.
     * The rotation axis must be non-zero unit vector, elsewhere
     * it's absolute value will affect the rotation angle.
     *
     * @param vector Reference to rotation axis vector.
     * @param angle Rotation angle in radians.
     * @return Rotation matrix.
     */
    static Matrix rotation(double angle, const Vector& vector);

    /**
     * Return string representation of this matrix.
     * @return Matrix elements in text string.
     */
    QString toString() const;

private:
    /// Matrix elements [row][column].
    double m[4][4];
};

/**
 * Allows printing the matrix using qDebug() << matrix
 * @param dbg The qDebug datastream.
 * @param matrix The matrix to be streamed in qDebug.
 */
QDebug operator <<(QDebug dbg, const Matrix& matrix);

#endif // MATRIX_H
